package com.pine.app.module.security.oauth.handler;


import com.pine.app.module.security.core.SecurityContextHolder;
import com.pine.app.module.security.core.common.*;
import com.pine.app.module.security.core.common.enums.ErrorType;
import com.pine.app.module.security.core.common.enums.GrantType;
import com.pine.app.module.security.core.common.enums.ResponseProduceType;
import com.pine.app.module.security.core.common.enums.ResponseType;
import com.pine.app.module.security.core.session.AuthenticationSessionStore;
import com.pine.app.module.security.core.session.impl.AuthenticationSessionStoreImpl;
import com.pine.app.module.security.core.userdetails.UserDetails;
import com.pine.app.module.security.oauth.annotation.FrameworkEndpoint;
import com.pine.app.module.security.oauth.config.properties.OAuth2Properties;
import com.pine.app.module.security.oauth.exception.AuthenticationException;
import com.pine.app.module.security.oauth.plugin.Plugin;
import com.pine.app.module.security.oauth.plugin.impl.loginTGT.OauthTgtDTO;
import com.pine.app.module.security.oauth.plugin.impl.loginTGT.OauthTgtService;
import com.pine.app.module.security.oauth.provider.DefaultOauth2TokenBuilder;
import com.pine.app.module.security.oauth.provider.OAuth2Authentication;
import com.pine.app.module.security.oauth.provider.authrizationCode.AuthenticationCodeService;
import com.pine.app.module.security.oauth.provider.client.ClientDetails;
import com.pine.app.module.security.oauth.provider.client.ClientDetailsService;
import com.pine.app.module.security.oauth.provider.token.AccessTokenService;
import com.pine.app.module.security.oauth.provider.token.OAuth2AccessToken;
import com.pine.app.module.security.oauth.support.code.AuthorizationCodeRequest;
import com.pine.app.module.security.oauth.support.code.HttpAuthorizationCodeRequest;
import com.pine.app.module.security.oauth.support.password.HttpPasswordRequest;
import com.pine.app.module.security.oauth.user.service.LoginService;
import com.pine.app.module.security.oauth.user.service.UserDetailService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.ModelMap;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;


/**
 * @author xiaoyuan
 * @create 2019/9/6 15:27
 **/
@FrameworkEndpoint
@RequestMapping("/oauth")
@Slf4j
public class Oauth2Handler extends BaseHandler {

    private AccessTokenService accessTokenService;

    private AuthenticationCodeService codeService;

    private UserDetailService userDetailService;


    private LoginService loginService;

    private ClientDetailsService clientDetailsService;

    private OAuth2Properties oAuth2Properties;


    private AuthenticationSessionStore authenticationSessionStore = new AuthenticationSessionStoreImpl();

//    public Oauth2Handler(AccessTokenService accessTokenService,AuthenticationCodeService codeService,
//                         UserDetailService userDetailService,
//                         LoginService loginService,
//                         AuthenticationSessionStore authenticationSessionStore ){
//        this.accessTokenService = accessTokenService;
//        this.codeService = codeService;
//        this.userDetailService = userDetailService;
//        this.loginService = loginService;
//        this.authenticationSessionStore = authenticationSessionStore;
//
//
//    }


    @Autowired
    public void setAccessTokenService(AccessTokenService accessTokenService) {
        this.accessTokenService = accessTokenService;
    }

    @Autowired
    public void setCodeService(AuthenticationCodeService codeService) {
        this.codeService = codeService;
    }

    @Autowired
    public void setUserDetailService(UserDetailService userDetailService) {
        this.userDetailService = userDetailService;
    }

    @Autowired
    public void setLoginService(LoginService loginService) {
        this.loginService = loginService;
    }


    @Autowired
    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    private OauthTgtService oauthTgtService;
//    public LoginPageTGTPlugin(OauthTgtService oauthTgtService ){
//         this.oauthTgtService = oauthTgtService;
//    }


    @Autowired
    public void setOauthTgtService(OauthTgtService oauthTgtService) {
        this.oauthTgtService = oauthTgtService;
    }

    @Autowired
    public void setoAuth2Properties(OAuth2Properties oAuth2Properties) {
        this.oAuth2Properties = oAuth2Properties;
    }

    /**
     * @Description: oauth2四大模式的入口
     * @Author: xiaoyuan
     * @Date: 2020/3/6 15:25
     */

    @RequestMapping(value = "/token", method = RequestMethod.POST)
    @ResponseBody
    public OAuth2AccessToken token(HttpServletRequest request) {

        OAuth2AccessToken accessToken = new DefaultOauth2TokenBuilder().createRequest(request).validateParams().createToken();
        if (accessToken == null) {
            ErrorType.SERVICE_ERROR.throwThis(AuthenticationException::new);
        }
        return accessToken;

    }


    /**
     * @Description: 检查token
     * @Author: xiaoyuan
     * @Date: 2020/3/6 15:25
     */
    @RequestMapping(value = "/check_token", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> checkToken(@RequestParam(value = "token") String token) {
        log.debug("token校验传入accessToken：" + token);
        OAuth2AccessToken auth2AccessToken = accessTokenService.getAccessToken(token);
        if (auth2AccessToken == null) {
            ErrorType.INVALID_TOKEN.throwThis(AuthenticationException::new);
        }
        if (auth2AccessToken.isExpire()) {
            ErrorType.EXPIRED_TOKEN.throwThis(AuthenticationException::new);
        }
        OAuth2Authentication oAuth2Authentication = this.accessTokenService.loadAuthentication(auth2AccessToken.getAccessToken());
        Map<String, Object> result = new HashMap<>();
        UserDetails userDetails = oAuth2Authentication.getUserDetails();
        if (userDetails != null) {
            result.put("user_name", userDetails.getUsername());
            result.put("id", userDetails.getId());
            if (!CollectionUtils.isEmpty(userDetails.getResources())) {
                result.put("resources", String.join(",", userDetails.getResources()));
            }
        }
        result.put("client_id", oAuth2Authentication.getTokenRequest().getClientId());
        result.put("grant_type", oAuth2Authentication.getTokenRequest().getGrantType());
        if (!CollectionUtils.isEmpty(oAuth2Authentication.getTokenRequest().getScope())) {
            result.put("scopes", String.join(",", oAuth2Authentication.getTokenRequest().getScope()));
        }

//        result.put("resources", userDetails.getResources());
//        result.put("roles", userDetails.getRoles());
        result.put("active", true);
        if (auth2AccessToken.getExpiresIn() != null) {
            result.put("exp", Long.valueOf(auth2AccessToken.getExpiresIn()));
        }
        return result;
    }

    /**
     * @Description: 表单登陆接口
     * @Author: xiaoyuan
     * @Date: 2020/3/5 13:45
     */
    @RequestMapping(value = "/authorize", method = RequestMethod.POST)
   // @Plugin(value = {"loginAPI", "clientVlidate"}, responseType = ResponseProduceType.HTML)
    public String authorize(HttpServletRequest request, HttpServletResponse response) {
        String redirectUrl = null;
        try {
            validateParams((request));
            // log.info("开始执行登陆接口的票据处理插件");
            //添加一个票据   目的是异常后跳转页面后也有此票据
            request.setAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY, oauthTgtService.createTGT());
            //转换为授权码请求对象
            AuthorizationCodeRequest authorizationCodeRequest = new HttpAuthorizationCodeRequest(request);
            //获取参数中的票据
            String p_tgt = authorizationCodeRequest.getParameter(AuthConstant.OAUTH_SERVER_COOKIE_KEY).orElse("");
            String error_message = "";
            if (StringUtils.isEmpty(p_tgt) || !this.oauthTgtService.checkTGT(p_tgt)) {
                throw new AuthenticationException(ErrorType.ILLEGAL_REQUEST, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
            }
            //检查用户信息
            UserDetails userDetails = loginService.login(new HttpPasswordRequest(request), ResponseProduceType.HTML);

            //如果responseType的类型为token 简化模式  那么就直接返回token
            if (ResponseType.TOKEN.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
                OAuth2AccessToken token = new DefaultOauth2TokenBuilder().createToken(authorizationCodeRequest, userDetails);
                redirectUrl = redirectUrlWithAccessToken(authorizationCodeRequest.getRedirectUri(),
                        authorizationCodeRequest.getParameter(AuthConstant.state).orElse(""), token);
            }
            //如果responseType的类型为code 授权码模式  那么就直接返回code
            if (ResponseType.CODE.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
                String code = codeService.createCode(new DefaultOauth2TokenBuilder().createOAuth2Authentication(authorizationCodeRequest, userDetails));
                redirectUrl = redirectUrlWithCode(authorizationCodeRequest.getRedirectUri(),
                        authorizationCodeRequest.getParameter(AuthConstant.state).orElse(""),
                        code);
            }
            //票据的后续处理
            OauthTgtDTO tgtDTO = new OauthTgtDTO();
            tgtDTO.setId(p_tgt);
            tgtDTO.setExpire(oAuth2Properties.getTgt().getExpire() / 1000);
            tgtDTO.setLoginName(authorizationCodeRequest.getParameter(AuthConstant.username).get());
            tgtDTO.setRememberMe("true".equalsIgnoreCase(request.getParameter("rememberMe")) ? true : false);
            tgtDTO.setRequestIp(RequestUtils.getIpAddr(request));
            tgtDTO.setUserAgent(request.getHeader("User-Agent"));
            //登陆成功后添加票据到cookie中
            CookieUtil.setCookie(response, AuthConstant.OAUTH_SERVER_COOKIE_KEY, p_tgt, 604800, true, false);
            //绑定票据和用户的关系到缓存
            this.oauthTgtService.save(tgtDTO);
            this.oauthTgtService.remove((String) request.getAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY));
            request.removeAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY);
        } catch (Exception e) {
            log.error("登录接口错误：",e);
            if(StringUtils.isNotEmpty(e.getMessage())){
                request.setAttribute(AuthConstant.DEFAULT_LOGIN_ERROR_KEY, e.getMessage());
            }
            return AuthConstant.DEFAULT_LOGIN_ERROR_PAGE;
        }
        return AuthConstant.REDIRECT_URI_PREFIX + redirectUrl;
    }

    /**
     * @Description: 未登陆时跳转到登陆页面，登陆过会跳转到传递的跳转页面
     * @Author: xiaoyuan
     * @Date: 2020/3/5 13:50
     */
    @RequestMapping(value = "/authorize", method = RequestMethod.GET)
    //   @Plugin(value = {"loginPage","clientVlidate"})
    public String login(HttpServletRequest request, ModelMap model) {
        String redirectUrl = null;
        try {
            validateParams((request));
            // log.info("开始执行登陆页面的票据处理插件");
            //添加一个票据   木的是异常后跳转页面后也有此票据
            request.setAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY, oauthTgtService.createTGT());
            //获取cookie中的票据信息
            String tgcCookieValue = CookieUtil.getCookie(request, AuthConstant.OAUTH_SERVER_COOKIE_KEY);
            //检查缓存中是否有此票据
            if (StringUtils.isEmpty(tgcCookieValue)) {
                throw new AuthenticationException(ErrorType.BLAK_MESSAGE, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
            }
            OauthTgtDTO tgtDTO = this.oauthTgtService.getOauthTgtDTO(tgcCookieValue);
            if (tgtDTO == null) {
                throw new AuthenticationException(ErrorType.ILLEGAL_REQUEST, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
            }
            //获取请求数据
            AuthorizationCodeRequest authorizationCodeRequest = new HttpAuthorizationCodeRequest(request);
            //通过票据关联的登陆名获取用户信息
            UserDetails userDetails = userDetailService.loadUserByUsername(tgtDTO.getLoginName());
            if (userDetails == null) {
                return AuthConstant.DEFAULT_LOGIN_PAGE;
            }

            //如果responseType的类型为token 简化模式  那么就直接返回token
            if (ResponseType.TOKEN.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
                OAuth2AccessToken token = new DefaultOauth2TokenBuilder().createToken(authorizationCodeRequest, userDetails);
                redirectUrl = redirectUrlWithAccessToken(authorizationCodeRequest.getRedirectUri(),
                        authorizationCodeRequest.getParameter(AuthConstant.state).orElse(""),
                        token);
            }
            //如果responseType的类型为code 授权码模式  那么就直接返回code
            if (ResponseType.CODE.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
                String code = codeService.createCode(new DefaultOauth2TokenBuilder().createOAuth2Authentication(authorizationCodeRequest, userDetails));
                redirectUrl = redirectUrlWithCode(authorizationCodeRequest.getRedirectUri(),
                        authorizationCodeRequest.getParameter(AuthConstant.state).orElse(""),
                        code);
            }
            //流程到此步时，那么移除掉开始生成的票据
            this.oauthTgtService.remove((String) request.getAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY));
            request.removeAttribute(AuthConstant.OAUTH_SERVER_COOKIE_KEY);
        } catch (Exception e) {
            log.error("登录接口错误：",e);
            if(StringUtils.isNotEmpty(e.getMessage())){
                request.setAttribute(AuthConstant.DEFAULT_LOGIN_ERROR_KEY, e.getMessage());
            }
            return AuthConstant.DEFAULT_LOGIN_ERROR_PAGE;
        }

        //跳转到重定向地址
        return AuthConstant.REDIRECT_URI_PREFIX + redirectUrl;
    }

    @RequestMapping(value = "/login")
    public String loginPage(HttpServletRequest request, ModelMap model) {
        //移除掉上一次的异常信息
        //  request.removeAttribute(AuthConstant.DEFAULT_LOGIN_ERROR_KEY);
        //model.remove(AuthConstant.DEFAULT_LOGIN_ERROR_KEY);
        return AuthConstant.DEFAULT_LOGIN_PAGE;
    }


    /**
     * @Description: 登出接口
     * @Author: xiaoyuan
     * @Date: 2020/3/6 15:26
     */
    @PostMapping("/logout")
    @ResponseBody
    public Result logout(@RequestParam(value = "access_token", required = false) String accessToken, HttpServletRequest request, HttpServletResponse response) {
        // 从请求头获取前端传过来的token，格式：token_type access_token
        String token = request.getHeader("Authorization");

        if (StringUtils.isNotBlank(token)) {
            // 获取access_token
            token = token.split(" ")[1];
        }
        //如果token不是放置header中，那么直接从参数中获取
        if (StringUtils.isBlank(token)) {
            token = accessToken;
        }
        //如果token还是为空，那么提示错误
        if (StringUtils.isBlank(token)) {
            ErrorType.TOKEN_NOT_NULL.throwThis(AuthenticationException::new);
        }
        SecurityContextHolder.clearContext();
        CookieUtil.deleteCookie(request, response, AuthConstant.OAUTH_SERVER_COOKIE_KEY);
        this.authenticationSessionStore.removeAuthtication(request);
        // 移除token，使得用户退出登录
        this.accessTokenService.removeToken(token);
        return Result.build(200, "退出成功");
    }


    private String redirectUrlWithCode(String redirectUri, String state, String code) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(redirectUri);
        if (StringUtils.containsIgnoreCase(redirectUri, "?")) {
            stringBuilder.append("&");
        } else {
            stringBuilder.append("?");
        }
        stringBuilder.append(AuthConstant.code);
        stringBuilder.append("=");
        stringBuilder.append(code);
        //如果有随机码，那么添加到后面
        if (StringUtils.isNotBlank(state)) {
            stringBuilder.append("&");
            stringBuilder.append(AuthConstant.state);
            stringBuilder.append("=");
            stringBuilder.append(state);
        }

        return stringBuilder.toString();
    }

    private String redirectUrlWithAccessToken(String redirectUri, String state, OAuth2AccessToken oauthToken) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(redirectUri);
        stringBuilder.append("#");
        stringBuilder.append(AuthConstant.access_token);
        stringBuilder.append("=");
        stringBuilder.append(oauthToken.getAccessToken());
        stringBuilder.append("&");
        stringBuilder.append(AuthConstant.token_type);
        stringBuilder.append("=");
        stringBuilder.append(AuthConstant.token_type_value);
        stringBuilder.append("&");
        stringBuilder.append(AuthConstant.expire_in);
        stringBuilder.append("=");
        stringBuilder.append(oauthToken.getExpiresIn());
        if (StringUtils.isNotEmpty(oauthToken.getRefreshToken())) {
            stringBuilder.append("&");
            stringBuilder.append(AuthConstant.refresh_token);
            stringBuilder.append("=");
            stringBuilder.append(oauthToken.getRefreshToken());
        }
        //如果有随机码，那么添加到后面
        if (StringUtils.isNotBlank(state)) {
            stringBuilder.append("&");
            stringBuilder.append(AuthConstant.state);
            stringBuilder.append("=");
            stringBuilder.append(state);
        }
        return stringBuilder.toString();
    }


    private void validateParams(HttpServletRequest request) {
        // log.info("开始执行登陆页面和登陆接口的参数校验插件");
        //移除掉上一次的异常信息
        request.removeAttribute(AuthConstant.DEFAULT_LOGIN_ERROR_KEY);
        //获取请求数据
        AuthorizationCodeRequest authorizationCodeRequest = new HttpAuthorizationCodeRequest(request);
        //检查客户端信息是否合法   如果通过返回客户端信息
        ClientDetails clientDetails = checkClientInfo(authorizationCodeRequest.getClientId());
        //把客户端的名字放到页面中
        request.setAttribute(AuthConstant.APP_NAME_KEY, clientDetails.getClientName());
        //检查responseType的类型是否错误
        checkResponseType(authorizationCodeRequest.getResponseType());
        //如果responseType的类型为token 简化模式  那么就直接返回token
        if (ResponseType.TOKEN.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
            if (!clientDetails.getAuthorizedGrantTypes().contains(GrantType.IMPLICIT.getName())) {
                throw new AuthenticationException(ErrorType.UNSUPPORTED_GRANT_TYPE, "此客户端不支持简化模式", ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
            }
        }
        //如果responseType的类型为code 授权码模式  那么就直接返回code
        if (ResponseType.CODE.getCode().equalsIgnoreCase(authorizationCodeRequest.getResponseType())) {
            if (!clientDetails.getAuthorizedGrantTypes().contains(GrantType.AUTHORIZATION_CODE.getName())) {
                throw new AuthenticationException(ErrorType.UNSUPPORTED_GRANT_TYPE, "此客户端不支持授权码模式", ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
            }
        }
        //检查redirectUri是否正确
        checkRedirectUri(authorizationCodeRequest.getRedirectUri());
        //检查跳转页面是否为注册的页面
        if (clientDetails.getRegisteredRedirectUri() == null || !clientDetails.getRegisteredRedirectUri().contains(authorizationCodeRequest.getRedirectUri())) {
            throw new AuthenticationException(ErrorType.ILLEGAL_REDIRECT_URI, "跳转链接不是注册时的跳转链接", ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
    }


    private ClientDetails checkClientInfo(String clientId) {

        if (StringUtils.isBlank(clientId)) {
            throw new AuthenticationException(ErrorType.CLIENT_NOT_NULL, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
        ClientDetails clientDetails = this.clientDetailsService.loadClientDetail(clientId);
        if (clientDetails == null || clientDetails.isExpiredTime()) {
            throw new AuthenticationException(ErrorType.ILLEGAL_CLIENT_ID, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
        return clientDetails;
    }

    private void checkResponseType(String responseType) {
        if (StringUtils.isBlank(responseType)) {
            throw new AuthenticationException(ErrorType.RESPONSE_TYPE_NOT_NULL, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
        ResponseType type = ResponseType.getByCode(responseType);
        if (type == null) {
            throw new AuthenticationException(ErrorType.UNSUPPORTED_RESPONSE_TYPE, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
    }

    private void checkRedirectUri(String redirectUri) {
        if (StringUtils.isBlank(redirectUri)) {
            throw new AuthenticationException(ErrorType.REDIRECT_URI_NOT_NULL, ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }
        if (!redirectUri.matches("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]")) {
            throw new AuthenticationException(ErrorType.REDIRECT_URI_NOT_NULL, "redirect_uri错误，非uri连接", ResponseProduceType.HTML, AuthConstant.DEFAULT_LOGIN_PATH);
        }

    }

}
